//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentUtilities
{
    using System;
    using System.Collections.ObjectModel;
    using System.Linq;
    using Microsoft.Research.Authentication;
    using Microsoft.Research.DataLayer;
    using System.Globalization;

    public enum UserRole
    {
        Admin,
        NormalUser,
        Guest,
        Service,
        Invalid
    }

    public class TridentAuthentication
    {
        private BaseAuthentication authentication;
        private ICredentials credential;
        private static Connection registryConnection;
        private static User loggedInUser;
        private static User systemUser;

        /// <summary>
        /// Gets or sets the logged in user.
        /// </summary>
        /// <value>The logged in user.</value>
        public static User LoggedInUser
        {
            get
            {
                if (TridentAuthentication.loggedInUser == null)
                {
                    TridentAuthentication.loggedInUser = User.Load(SYSTEMUSER, TridentAuthentication.registryConnection);
                }

                return TridentAuthentication.loggedInUser;
            }

            private set
            {
                TridentAuthentication.loggedInUser = value;
            }
        }

        /// <summary>
        /// Gets the system user.
        /// </summary>
        /// <value>The system user.</value>
        public static User SystemUser
        {
            get
            {
                if (TridentAuthentication.systemUser == null)
                {
                    TridentAuthentication.systemUser = User.Load(SYSTEMUSER, TridentAuthentication.registryConnection);
                }

                return TridentAuthentication.systemUser;
            }
        }

        /// <summary>
        /// Gets or sets the system role.
        /// </summary>
        /// <value>The system role.</value>
        public static SystemRole SystemRole { get; set; }

        /// <summary>
        /// Gets or sets the logged user role.
        /// </summary>
        /// <value>The logged user role.</value>
        /// 
        public static UserRole LoggedUserRole { get; set; }
        /// <summary>
        /// Gets or sets the logged group role.
        /// </summary>
        /// <value>The logged group role.</value>
        private static UserRole LoggedGroupRole { get; set; }

        /// <summary>
        /// Gets or sets the name of the logged user in user.
        /// </summary>
        /// <value>The name of the logged user in user.</value>
        public static string LoggedUserInUserName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        private const string UserRootName = "UserWorkflowRoot";

        /// <summary>
        /// 
        /// </summary>
        public const string INVALIDLOGINERROR = "User not authorized. Cannot open the application";

        private const string SYSTEMUSER = "SYSTEM";

        /// <summary>
        /// User credential name for anonymous user.
        /// </summary>
        private const string ANONYMOUS_USERCREDENTIAL_NAME = "ANONYMOUS";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="registryConnection"></param>
        public TridentAuthentication(Connection registryConnection)
        {
            this.authentication = new ADAuthentication();

            if (Environment.UserName.ToUpper(CultureInfo.InvariantCulture).Equals(SYSTEMUSER))
            {
                String credentialName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", "NT Authority", Environment.UserName);
                this.credential = new Credentials(credentialName, Environment.UserName, "NT Authority", Microsoft.Research.Authentication.CredentialType.User);
                TridentAuthentication.registryConnection = registryConnection;
            }
            else
            {
                String credentialName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", Environment.UserDomainName, Environment.UserName);
                this.credential = new Credentials(credentialName, Environment.UserName, Environment.UserDomainName, Microsoft.Research.Authentication.CredentialType.User);
                TridentAuthentication.registryConnection = registryConnection;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TridentAuthentication"/> class.
        /// </summary>
        /// <param name="domain">The domain.</param>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <param name="registryConnection">The registry connection.</param>
        public TridentAuthentication(string domain, string userName, Connection registryConnection)
        {
            this.authentication = new ADAuthentication();

            if (userName.ToUpper(CultureInfo.InvariantCulture).Equals(SYSTEMUSER))
            {
                String credentialName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", "NT Authority", userName);
                this.credential = new Credentials(credentialName, userName, "NT Authority", Microsoft.Research.Authentication.CredentialType.User);
                TridentAuthentication.registryConnection = registryConnection;
            }
            else if (userName.ToUpper(CultureInfo.InvariantCulture).Equals(TridentAuthentication.ANONYMOUS_USERCREDENTIAL_NAME))
            {
                String credentialName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", "NT Authority", "NETWORK SERVICE");
                this.credential = new Credentials(credentialName, "NETWORK SERVICE", "NT Authority", Microsoft.Research.Authentication.CredentialType.User);
                TridentAuthentication.registryConnection = registryConnection;
            }
            else
            {
                String credentialName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", domain, userName);
                this.credential = new Credentials(credentialName, userName, domain, Microsoft.Research.Authentication.CredentialType.User);
                TridentAuthentication.registryConnection = registryConnection;
            }
        }

        /// <summary>
        /// Authendicates the user.
        /// </summary>
        public void AuthenticateUser()
        {
            if (!this.IsRegistryCredentiial())
            {
                if (TridentAuthentication.CheckGlobalSettings())
                {
                    if (!this.IsGroupInRegistry())
                    {
                        throw new TridentCustomException(TridentErrorConstants.ErrorNumber1000000072, TridentAuthentication.INVALIDLOGINERROR);
                    }
                }
                else
                {
                    throw new TridentCustomException(TridentErrorConstants.ErrorNumber1000000072, TridentAuthentication.INVALIDLOGINERROR);
                }
            }

            TridentAuthentication.LoggedUserRole = GetUserRoles();

            if (TridentAuthentication.LoggedUserRole == UserRole.Invalid)
            {
                throw new TridentCustomException("No roles have been assigned to you. Kindly contact Trident Administrator");
            }
        }

        /// <summary>
        /// Determines whether [is registry credentiial].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is registry credentiial]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsRegistryCredentiial()
        {
            // Need to change the filter
            return User.CommonSearches.UserCredentialExistInRegistry(this.credential.Name, TridentAuthentication.registryConnection);
        }

        /// <summary>
        /// Gets the user roles.
        /// </summary>
        /// <returns></returns>
        private UserRole GetUserRoles()
        {
            User user = null;
            try
            {
                user = UserCredential.LoadAll(TridentAuthentication.registryConnection).FirstOrDefault(p => p.Credential.ToUpper(CultureInfo.InvariantCulture).Equals(this.credential.Name.ToUpper(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)
                    && p.User.Enabled == true && p.User.IsDeleted == false).User;
                if (user != null)
                {
                    TridentAuthentication.loggedInUser = user;
                    LoggedUserInUserName = user.Name;
                    TridentAuthentication.LoggedGroupRole = TridentAuthentication.GetHighestPriorityGroup(user);
                }
                else
                {
                    return UserRole.Invalid;
                }
            }
            catch (RecordNotFoundException)
            {

                return UserRole.Invalid;
            }

            SystemRole userAdminRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Administrators));

            if (userAdminRole != null || LoggedGroupRole == UserRole.Admin)
            {
                return UserRole.Admin;
            }

            SystemRole serviceRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Services));
            if (serviceRole != null || LoggedGroupRole == UserRole.Service)
            {
                // Service will be treated as Admin
                return UserRole.Admin;
            }
            SystemRole userRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Users));
            if (userRole != null || LoggedGroupRole == UserRole.NormalUser)
            {
                return UserRole.NormalUser;
            }

            SystemRole guestRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Guests));
            if (guestRole != null || LoggedGroupRole == UserRole.Guest)
            {
                return UserRole.Guest;
            }

            return UserRole.Invalid;
        }

        /// <summary>
        /// Gets the highest priority group.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        private static UserRole GetHighestPriorityGroup(User user)
        {
            foreach (Group grp in user.Groups)
            {
                if ((!grp.IsDeleted && grp.Enabled))
                {
                    SystemRole adminRole = grp.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Administrators));

                    if (adminRole != null)
                    {
                        return UserRole.Admin;
                    }

                    SystemRole serviceRole = grp.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Services));
                    if (serviceRole != null)
                    {
                        return UserRole.Admin;
                    }
                    SystemRole userRole = grp.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Users));
                    if (userRole != null)
                    {
                        return UserRole.NormalUser;
                    }

                    SystemRole guestRole = grp.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Guests));
                    if (guestRole != null)
                    {
                        return UserRole.Guest;
                    }
                }
            }

            return UserRole.Invalid;
        }

        /// <summary>
        /// Adds the roles for user in group.
        /// </summary>
        /// <param name="securityPrincipal">The security principal.</param>
        /// <param name="user">The user.</param>
        private static void AddRolesForUserInGroup(Group securityPrincipal, User user)
        {
            SystemPolicy systemPolicy = SystemPolicy.Get(TridentAuthentication.registryConnection);

            SystemRole adminRole = securityPrincipal.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Administrators));
            if (adminRole != null)
            {
                systemPolicy.Builtin.Administrators.Add(user);
                systemPolicy.Save();
            }

            SystemRole userRole = securityPrincipal.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Users));
            if (userRole != null)
            {
                systemPolicy.Builtin.Users.Add(user);
                systemPolicy.Save();
            }

            SystemRole guestRole = securityPrincipal.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Guests));
            if (guestRole != null)
            {
                systemPolicy.Builtin.Guests.Add(user);
                systemPolicy.Save();
            }

        }

        /// <summary>
        /// Determines whether [is group in registry].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is group in registry]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsGroupInRegistry()
        {
            Collection<String> groups = this.authentication.GetGroups(this.credential);
            foreach (string groupName in groups)
            {
                try
                {
                    Group registryGroup = Group.CommonSearches.GetGroupCredential(groupName, TridentAuthentication.registryConnection);
                    if (registryGroup != null)
                    {
                        // Add the User Credential
                        this.AddUserCredentialInRegistry(registryGroup);

                        return true;
                    }
                }
                catch (RecordNotFoundException)
                {
                    // Ignore this exception
                }
            }

            LocalAuthentication localAuthentication = new LocalAuthentication();
            Collection<String> localGroups = localAuthentication.GetGroups(this.credential);
            foreach (string groupName in localGroups)
            {
                try
                {
                    Group registryGroup = Group.CommonSearches.GetGroupCredential(groupName, TridentAuthentication.registryConnection);
                    if (registryGroup != null)
                    {
                        // Add the User Credential
                        this.AddUserCredentialInRegistry(registryGroup);

                        return true;
                    }

                }
                catch (RecordNotFoundException)
                {
                    // Ignore this exception
                }
            }

            return false;
        }

        /// <summary>
        /// Checks the global settings.
        /// </summary>
        /// <returns></returns>
        private static bool CheckGlobalSettings()
        {
            SystemPolicy syspolicy = SystemPolicy.Get(TridentAuthentication.registryConnection);
            if (syspolicy != null)
            {
                return syspolicy.AutoCreateUserIfGroupMatch;
            }

            return false;
        }

        /// <summary>
        /// Adds the user credential in registry.
        /// </summary>
        /// <param name="group">The group.</param>
        public void AddUserCredentialInRegistry(Group group)
        {
            User user = User.Create(this.credential.UserName, TridentAuthentication.registryConnection);
            user.Enabled = true;
            user.Save();

            UserCredential userCredential = UserCredential.Create(credential.Name, Microsoft.Research.DataLayer.CredentialType.Windows, user, TridentAuthentication.registryConnection);
            userCredential.Save();

            // Create a workflow root if it doesnt exist.
            CreateMyWorkflowCategory(user, TridentAuthentication.registryConnection);

            SystemPolicy policy = null;
            try
            {
                policy = SystemPolicy.Get(TridentAuthentication.registryConnection);
            }
            catch (Exception)
            {
            }

            // Add the Group
            if (group != null)
            {
                group.Users.Add(user);
                group.Save();
                TridentAuthentication.AddRolesForUserInGroup(group, user);
            }

        }

        /// <summary>
        /// Creates my workflow category.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="registryConnection">The registry connection.</param>
        public static void CreateMyWorkflowCategory(User user, Connection registryConnection)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            string myWorkflowText = TridentResourceManager.GetString("WorkFlowTreeHeader");
            if (user.WorkflowRoot == null)
            {
                Namespace userRoot = null;
                try
                {
                    userRoot = Namespace.Load(UserRootName, registryConnection);
                }
                catch (RecordNotFoundException)
                {

                }
                if (userRoot == null)
                {
                    userRoot = Namespace.Create(UserRootName, registryConnection);
                    userRoot.IsEditable = false;
                    userRoot.Save();
                }

                if (user.WorkflowRoot == null)
                {
                    user.WorkflowRoot = userRoot;
                    user.Save();
                }

                // Create a namespace signifying the user's private workflows.
                Namespace myWorkflow = Namespace.Create(myWorkflowText, registryConnection);
                myWorkflow.Label = myWorkflowText;
                myWorkflow.User = user;
                myWorkflow.IsEditable = false;
                myWorkflow.Parent = user.WorkflowRoot;

                // Save the changes.
                myWorkflow.Save();
            }
        }

        /// <summary>
        /// Determines whether teh current user has the permission to delete the workflow with teh specified ownerID.
        /// </summary>
        /// <param name="ownerId">The worklfow owner id.</param>
        /// <returns>
        /// 	<c>true</c> if this workflow specified owner ID can be deleted by the current user; otherwise, <c>false</c>.
        /// </returns>
        public static bool CanUserDeleteWorkflow(Guid ownerId)
        {
            bool canDelete = false;

            if (TridentAuthentication.LoggedUserRole == UserRole.Admin)
            {
                canDelete = true;
            }
            else if (
                TridentAuthentication.LoggedUserRole == UserRole.NormalUser &&
                ownerId != null &&
                ownerId.Equals(((IObject)TridentAuthentication.LoggedInUser).ID))
            {
                canDelete = true;
            }

            return canDelete;
        }
    }
}
